#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include "GenericException.hpp"
#include "ListenSocket.hpp"
#include "TCPConnection.hpp"
#include "Logger.hpp"

ListenSocket::ListenSocket(const IP& interfaces, unsigned int port) {
	fd = socket(PF_INET, SOCK_STREAM, 0);
	if (fd == -1) {
		GENERICSTRMEXCEPTION("Cannot create socket: " << strerror(errno))
	}

	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	if (interfaces.isZero()) {
		LOGSTRM(Logger::INFO, "Binding to TCP port " << port << " on all interfaces.");
		sin.sin_addr.s_addr = htonl(INADDR_ANY);
	} else {
		LOGSTRM(Logger::INFO, "Only binding to IP address " << interfaces.getStr() << ", TCP port " << port);
		sin.sin_addr.s_addr = htonl(interfaces.getInt());
	}
	if (bind(fd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
		GENERICSTRMEXCEPTION("Cannot bind socket to interface " << interfaces.getStr() << ": " << strerror(errno))
	}

	if (listen(fd, 10) == -1) {
		GENERICSTRMEXCEPTION("Cannot listen on bound socket: " << strerror(errno))
	}
}

int ListenSocket::getConnection(IP* remoteIP) {
	int sd;
	struct sockaddr_in remote;
	socklen_t length = sizeof(remote);
	if ((sd = accept(fd, (struct sockaddr*)&remote, &length)) == -1) {
		GENERICSTRMEXCEPTION("Cannot accept on bound socket: " << strerror(errno))
	}
	remoteIP->setTo((const char*)&remote.sin_addr.s_addr);
	return sd;
}

void ListenSocket::handleConnection(ConnectionHandler* handler) {
	IP remoteIP;
	int sd = getConnection(&remoteIP);
	pid_t pid;
	pid = fork();
	if (pid == -1) {
		GENERICSTRMEXCEPTION("Cannot fork: " << strerror(errno));
	}
	if (pid == 0) {
		/* Child */
		close(fd);
		TCPConnection connection = TCPConnection(sd, remoteIP);
		try {
			handler->handle(&connection);
		} catch (GenericException& e) {
			LOGSTRM(Logger::INFO, "Child: Connection from " << connection.getRemoteStr() << " closed, connection handler threw exception.");
		}
		LOGSTRM(Logger::INFO, "Child: Connection from " << connection.getRemoteStr() << " closed, handler returned.");
		close(sd);
		exit(EXIT_SUCCESS);
	} else {
		/* Parent */
		close(sd);
		LOGSTRM(Logger::DEBUG, "Parent: Fork successful, child PID is " << pid);
	}
}

ListenSocket::~ListenSocket() {
	LOGSTRM(Logger::DEBUG, "Listen socket closed.");
	close(fd);
}

